package com.kelin.calendarlistview.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.*;

import ohos.app.Context;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import ohos.multimodalinput.event.TouchEvent;

import java.lang.reflect.ParameterizedType;

import java.text.ParseException;

import java.util.Calendar;
import java.util.TreeMap;

/**
 * CalendarView
 */
public class CalendarView extends StackLayout {
    private static final int ROW_ITEM_COUNT = 7;
    private static final int COLUMN_ITEM_COUNT = 5;
    private static final int ITEM_HEIGHT = 40;
    private static int mItemWidth = 0;
    private static int mItemHeight = 0;
    private TreeMap<String, BaseCalendarItemModel> dayModelTreeMap;
    private OnDateSelectedListener onDateSelectedListener;
    protected BaseCalendarItemAdapter calendarItemAdapter;
    //gridview for contain calendar items
    protected ListContainer calendarGridView;
    //when selected one date,this view is floating on Calendar
    protected Component selectedFloatingView;
    //a view like toast to show Month Changed.
    private Text floatingMonthTips;
    private String currentMonth;
    private String selectedDate;
    //avoid to duplicate animation play.
    private boolean isMonthChanging = false;
    //use to custom your CalendarItemModel
    private Class<?> entityClass;
    private CalendarListView.OnMonthChangedListener onMonthChangedListener;
    private float downX;
    private float downY;

    /**
     * CalendarView instance
     *
     * @param context 上下文
     */
    public CalendarView(Context context) {
        super(context);
//        mItemWidth = CalendarHelper.getWidth() / ROW_ITEM_COUNT;
//        mItemHeight = mItemWidth + CalendarHelper.vp2px(context, "1vp");
        setItemSize(context);
    }

    private static void setItemSize(Context context) {
        mItemWidth = CalendarHelper.getWidth() / ROW_ITEM_COUNT;
        mItemHeight = mItemWidth + CalendarHelper.vp2px(context, "1vp");
    }

    /**
     * getmItemWidth
     *
     * @return int
     */
    public static int getmItemWidth() {
        return mItemWidth;
    }

    /**
     * getmItemHeight
     *
     * @return int
     */
    public static int getmItemHeight() {
        return mItemHeight;
    }

    protected void setOnMonthChangedListener(CalendarListView.OnMonthChangedListener onMonthChangedListener) {
        this.onMonthChangedListener = onMonthChangedListener;
    }

    protected String getSelectedDate() {
        return selectedDate;
    }

    protected void setOnDateSelectedListener(OnDateSelectedListener onDateSelectedListener) {
        this.onDateSelectedListener = onDateSelectedListener;
    }

    protected TreeMap<String, BaseCalendarItemModel> getDayModelTreeMap() {
        return dayModelTreeMap;
    }

    /**
     * setOnTouchEvent
     *
     * @param component 上下文
     * @param ev TouchEvent
     * @return boolean
     */
    public boolean setOnTouchEvent(Component component, TouchEvent ev) {
        switch (ev.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                downX = ev.getPointerScreenPosition(0).getX();
                downY = ev.getPointerScreenPosition(0).getY();
                break;
            case TouchEvent.POINT_MOVE:
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                float velocityX = (float) ((double)ev.getPointerScreenPosition(0).getX() - downX);
                float velocityY = (float) ((double)ev.getPointerScreenPosition(0).getY() - downY);
                if (!isMonthChanging) {
                    if (Math.abs(velocityY) > Math.abs(velocityX) && Math.abs(velocityY) >= 20) {
                        Calendar calendar = CalendarHelper.getCalendarByYearMonthDay(selectedDate);
                        calendar.add(Calendar.MONTH, velocityY < 0 ? 1 : -1);
                        changeMonth(velocityY < 0 ? 1 : -1, CalendarHelper.YEAR_MONTH_DAY_FORMAT.format(calendar.getTime()),
                                CalendarListView.Status.LIST_CLOSE);
                    }
                }
                break;
        }
        return false;
    }

    /**
     * getFloatingMonthTips
     *
     * @return Text
     */
    public Text getFloatingMonthTips() {
        return floatingMonthTips;
    }

    /**
     * getSelectedFloatingView
     *
     * @return Component
     */
    public Component getSelectedFloatingView() {
        return selectedFloatingView;
    }

    protected void initView() {
        Component content = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_layout_calendar_view, this, true);
        calendarGridView = (ListContainer) content.findComponentById(ResourceTable.Id_gridview);
        calendarGridView.setLongClickable(false);
        selectedFloatingView = content.findComponentById(ResourceTable.Id_selected_view);
        floatingMonthTips = (Text) findComponentById(ResourceTable.Id_floating_month_tip);
        floatingMonthTips.setVisibility(HIDE);
        new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
            @Override
            public void run() {
                StackLayout.LayoutConfig layoutParams = (StackLayout.LayoutConfig) floatingMonthTips.getLayoutConfig();
                layoutParams.setMarginTop(2 * mItemHeight - floatingMonthTips.getHeight() / 2);
                floatingMonthTips.setLayoutConfig(layoutParams);
            }
        });
        calendarGridView.setLayoutConfig(new StackLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
                COLUMN_ITEM_COUNT * mItemHeight));
        calendarItemAdapter.setGridClickedListener(component -> {
            BaseCalendarItemAdapter.TagPosition tag = (BaseCalendarItemAdapter.TagPosition) component.getTag();
            int position = tag.getPos();
            animateSelectedViewToPos(position);
            CalendarView.this.selectedDate = (String) calendarItemAdapter.getIndexToTimeMap().get(position);
            if (onDateSelectedListener != null) {
                onDateSelectedListener.onDateSelected(CalendarView.this, component, CalendarView.this.selectedDate, position);
            }
        });
        calendarGridView.setItemProvider(calendarItemAdapter);
        selectedFloatingView.setLayoutConfig(new StackLayout.LayoutConfig(mItemWidth, mItemHeight));
    }

    protected void animateSelectedViewToDate(String date) {
        selectedDate = date;
        int position = calendarItemAdapter.getIndexToTimeMap().indexOf(date);
        animateSelectedViewToPos(position);
    }

    protected void animateSelectedViewToPos(int position) {
        int left = mItemWidth * (position % ROW_ITEM_COUNT);
        int top = mItemHeight * (position / ROW_ITEM_COUNT);

        selectedFloatingView.setContentPosition(left, top);
    }

    protected void animateCalendarViewToDate(String date) {
        int position = calendarItemAdapter.getIndexToTimeMap().indexOf(date);
        int row = position / ROW_ITEM_COUNT;

        float curTY = getTranslationY();
        float byY = (float) (getHeight() * row / (((double)getHeight() / mItemHeight)) + curTY);
        AnimatorValue animator = new AnimatorValue();
        animator.setDuration(300);
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                setTranslationY((float) ((double)curTY - byY * v));
            }
        });
        animator.start();
    }

    protected void initCalendarView() {
        Calendar calendar = Calendar.getInstance();
        long selectedTime = calendar.getTimeInMillis();
        selectedDate = CalendarHelper.YEAR_MONTH_DAY_FORMAT.format(selectedTime);
        currentMonth = CalendarHelper.YEAR_MONTH_FORMAT.format(selectedTime);
        TreeMap<String, BaseCalendarItemModel> calendarItemModelTreeMap = getDefaultCalendarDataListByYearMonth(
                currentMonth);
        for (BaseCalendarItemModel model : calendarItemModelTreeMap.values()) {
            if (CalendarHelper.areEqualDays(model.getTimeMill(), selectedTime)) {
                model.setStatus(BaseCalendarItemModel.Status.SELECTED);
            }
        }
        setDayModelTreeMap(calendarItemModelTreeMap);
        int pos = calendarItemAdapter.getIndexToTimeMap().indexOf(selectedDate);
        animateSelectedViewToPos(pos);
    }

    private void setDayModelTreeMap(TreeMap<String, BaseCalendarItemModel> dayModelTreeMap) {
        this.dayModelTreeMap = dayModelTreeMap;
        updateCalendarView();
    }

    protected void setCalendarItemAdapter(BaseCalendarItemAdapter calendarItemAdapter) {
        this.calendarItemAdapter = calendarItemAdapter;
        entityClass = (Class<?>) ((ParameterizedType) calendarItemAdapter.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        initCalendarView();
    }

    private void updateCalendarView() {
        initView();
        calendarItemAdapter.setDayModelList(dayModelTreeMap);
        calendarItemAdapter.notifyDataChanged();
    }

    private TreeMap<String, BaseCalendarItemModel> getDefaultCalendarDataListByYearMonth(String yearMonth) {
        int calendarViewRow = COLUMN_ITEM_COUNT;
        int calendarViewColumn = ROW_ITEM_COUNT;

        Calendar calToday = Calendar.getInstance();
        Calendar calStartDate = Calendar.getInstance();
        calToday.setFirstDayOfWeek(Calendar.SUNDAY);
        calStartDate.setFirstDayOfWeek(Calendar.SUNDAY);
        long time = 0;
        try {
            time = CalendarHelper.YEAR_MONTH_FORMAT.parse(yearMonth).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        calStartDate.setTimeInMillis(time);
        calStartDate.set(Calendar.DAY_OF_MONTH, 1);
        calStartDate.set(Calendar.HOUR_OF_DAY, 0);
        calStartDate.set(Calendar.MINUTE, 0);
        calStartDate.set(Calendar.SECOND, 0);
        Calendar activeCalendar = (Calendar) calStartDate.clone();
        calStartDate.add(Calendar.DAY_OF_WEEK, -(calStartDate.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY));
        Calendar calEndDate = (Calendar) calStartDate.clone();
        calEndDate.add(Calendar.DAY_OF_MONTH, calendarViewRow * calendarViewColumn - 1);
        Calendar calCalendar = Calendar.getInstance();
        calCalendar.setTimeInMillis(calStartDate.getTimeInMillis());
        TreeMap<String, BaseCalendarItemModel> dayModelList = new TreeMap<>();
        for (int i = 0; i < calendarViewRow * calendarViewColumn; i++) {
            BaseCalendarItemModel baseCalendarItemModel = null;
            try {
                baseCalendarItemModel = (BaseCalendarItemModel) entityClass.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (baseCalendarItemModel != null) {
                baseCalendarItemModel.setCurrentMonth(CalendarHelper.areEqualMonth(calCalendar, activeCalendar));
                baseCalendarItemModel.setToday(CalendarHelper.areEqualDays(calCalendar.getTimeInMillis(), calToday.getTimeInMillis()));
                baseCalendarItemModel.setTimeMill(calCalendar.getTimeInMillis());
                baseCalendarItemModel.setHoliday(Calendar.SUNDAY == calCalendar.get(Calendar.DAY_OF_WEEK) ||
                        Calendar.SATURDAY == calCalendar.get(Calendar.DAY_OF_WEEK));
                baseCalendarItemModel.setDayNumber(String.valueOf(calCalendar.get(Calendar.DAY_OF_MONTH)));
                calCalendar.add(Calendar.DAY_OF_MONTH, 1);
                dayModelList.put(CalendarHelper.YEAR_MONTH_DAY_FORMAT.format(
                        baseCalendarItemModel.getTimeMill()), baseCalendarItemModel);
            }

        }
        return dayModelList;

    }

    protected void changeMonth(int offset, final String date, final CalendarListView.Status status) {
//        if (isMonthChanging) {
//            return;
//        }
        this.selectedDate = date;
        isMonthChanging = true;
        CalendarView oldCalendarView = new CalendarView(this.getContext());
        oldCalendarView.setCalendarItemAdapter(this.calendarItemAdapter);
        StackLayout container = (StackLayout) this.getComponentParent();
        container.addComponent(oldCalendarView);
//        oldCalendarView.setTranslationY(this.getTranslationY());
        Calendar calendar = CalendarHelper.getCalendarByYearMonthDay(selectedDate);
        currentMonth = CalendarHelper.YEAR_MONTH_FORMAT.format(calendar.getTime());
        TreeMap<String, BaseCalendarItemModel> calendarItemModelTreeMap = getDefaultCalendarDataListByYearMonth(
                currentMonth);
        setDayModelTreeMap(calendarItemModelTreeMap);
//        setTranslationY(offset * getHeight());
        animateCalendarViewToNewMonth(oldCalendarView, offset, oldCalendarView.getTranslationY(), new CalendarListView.OnMonthChangedListener() {
            @Override
            public void onMonthChanged(String yearMonth) {
                if (status == CalendarListView.Status.LIST_OPEN) {
                    animateCalendarViewToDate(date);
                }
                animateSelectedViewToDate(date);
            }
        });

    }

    private void animateCalendarViewToNewMonth(final CalendarView oldCalendarView, int offset, float translationY, final CalendarListView.OnMonthChangedListener monthChangeListener) {
        floatingMonthTips.setText(currentMonth);
        floatingMonthTips.setAlpha(1);
        AnimatorProperty alphaAnimator = floatingMonthTips.createAnimatorProperty();
        alphaAnimator.alpha(0f);
        alphaAnimator.setDuration(1500);
        alphaAnimator.setCurveType(Animator.CurveType.ACCELERATE);
        alphaAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                floatingMonthTips.setVisibility(VISIBLE);
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                floatingMonthTips.setVisibility(HIDE);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        alphaAnimator.start();

        AnimatorProperty animator = this.createAnimatorProperty();
        animator.moveFromY(offset * getHeight());
        animator.moveToY(0);
        animator.setDuration(800);
        animator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                StackLayout container = (StackLayout) getComponentParent();
                container.removeComponent(oldCalendarView);
                isMonthChanging = false;
                if (onMonthChangedListener != null) {
                    onMonthChangedListener.onMonthChanged(currentMonth);
                }
                if (monthChangeListener != null) {
                    monthChangeListener.onMonthChanged(currentMonth);
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        animator.start();

        AnimatorProperty animator2 = oldCalendarView.createAnimatorProperty();
        animator2.moveFromY(0);
        animator2.moveToY(- offset * getHeight());
        animator2.setDuration(800);
        animator2.start();
    }

    /**
     * getSelectedRowColumn
     *
     * @return SelectedRowColumn
     */
    public SelectedRowColumn getSelectedRowColumn() {
        Calendar firstItemCalendar = CalendarHelper.getCalendarByYearMonthDay(dayModelTreeMap.firstKey());
        Calendar selectedItemCalendar = CalendarHelper.getCalendarByYearMonthDay(selectedDate);
        int diff = CalendarHelper.getDiffDayByTimeStamp(firstItemCalendar.getTimeInMillis(), selectedItemCalendar.getTimeInMillis());
        SelectedRowColumn selectedRowColumn = new SelectedRowColumn();
        selectedRowColumn.column = (diff % ROW_ITEM_COUNT);
        selectedRowColumn.row = (diff / ROW_ITEM_COUNT);
        return selectedRowColumn;
    }

    /**
     * SelectedRowColumn
     */
    public static class SelectedRowColumn {
        public int row;
        public int column;
    }

    /**
     * OnDateSelectedListener
     */
    public interface OnDateSelectedListener {
        /**
         *
         * on date selected
         *
         * @param calendarView current view
         * @param view         the view(Calendar View Item) that was clicked.
         * @param time         the date has been selected with "yyyy-MM-dd" format
         * @param pos          position in GridView
         */
        void onDateSelected(CalendarView calendarView, Component view, String time, int pos);
    }
}
